package com.innjia.shiro.filter;

import com.innjia.base.common.Constant;
import com.innjia.base.enums.UserSysRoleEnum;
import com.innjia.base.exception.LoginException;
import com.innjia.base.utils.JsonUtil;
import com.innjia.base.utils.Result;
import com.innjia.shiro.utils.LoginToken;
import com.innjia.sys.entity.UserEntity;
import com.innjia.sys.entity.UserLandlordEntity;
import com.innjia.sys.entity.UserTenantEntity;
import com.innjia.utils.ShiroUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpStatus;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;

/**
 * <br>
 * 2017-11-15
 *
 * @author zhouxianglh@gmail.com
 */
public class InnjiaAuthenticationFilter extends AccessControlFilter {


    private static final Logger LOGGER = LoggerFactory.getLogger(InnjiaAuthenticationFilter.class);

    /**
     * 用来存放当前登录类型
     */
    private ThreadLocal<UserSysRoleEnum> loginType = new ThreadLocal<>();

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;

    /**
     * token 有效时长
     */
    @Value("${token.innjia.timeout}")
    private int timeOut;

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        if (null != ShiroUtils.getUserEntity()) {
            return true;
        } else {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            return "OPTIONS".equalsIgnoreCase(httpServletRequest.getMethod());
        }
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        try {
            Subject subject = ShiroUtils.getSubject();
            LoginToken loginToken = createToken(servletRequest, servletResponse);
            subject.login(loginToken);
            return true;
        } catch (LoginException e) {
            LOGGER.error("", e);
            writeResponse((HttpServletResponse) servletResponse, e.getResult());
            return false;
        } catch (Exception e) {
            LOGGER.error("", e);
            writeResponse((HttpServletResponse) servletResponse, null);
            return false;
        }
    }

    @Override
    protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
        //这里为了实现通过token 登录,所以重载此方法
        return true;
    }

    @Override
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        //        UserSysRoleEnum roleEnum = loginType.get();
        //        super.redirectToLogin(request, response);
    }

    protected LoginToken createToken(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("token");
        if (StringUtils.isBlank(token)) {
            return createWebToken(request);
        } else {
            return createAppToken(request, token);
        }
    }

    private void writeResponse(HttpServletResponse response, Result result) {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(HttpStatus.SC_FORBIDDEN);
        try {
            if (null == result) {
                result = Result.error("账号或密码不正确");
            }
            PrintWriter writer = response.getWriter();
            writer.write(JsonUtil.getJsonByObj(result));
            writer.flush();
            //这个不要close 后面还有别的操作
        } catch (IOException e) {
            LOGGER.error("", e);
        }
    }

    protected LoginToken createWebToken(ServletRequest request) {
        String username = WebUtils.getCleanParam(request, "username");
        String password = WebUtils.getCleanParam(request, "password");
        boolean rememberMe = this.isRememberMe(request);
        String host = this.getHost(request);
        loginType.set(UserSysRoleEnum.ADMIN);
        return LoginToken.buildAdmin(username, password, rememberMe, host);
    }

    private LoginToken createAppToken(ServletRequest request, String token) {
        String tokenKey = Constant.TOKEN__PREFIX + token;
        UserEntity userEntity = (UserEntity) redisTemplate.opsForValue().get(tokenKey);
        UserSysRoleEnum roleEnum = getLoginType(userEntity);
        loginType.set(roleEnum);
        //刷新有效期
        redisTemplate.expire(tokenKey, timeOut, TimeUnit.HOURS);
        return LoginToken.buildToken(request.getRemoteHost(), token, roleEnum);
    }

    protected boolean isRememberMe(ServletRequest request) {
        return WebUtils.isTrue(request, "rememberMe");
    }

    protected String getHost(ServletRequest request) {
        return request.getRemoteHost();
    }

    /**
     * 获取操作类型
     *
     * @param userEntity
     * @return
     */
    private UserSysRoleEnum getLoginType(UserEntity userEntity) {
        if (null == userEntity) {
            throw new LoginException("登录超时,请重新登录");
        } else if (userEntity instanceof UserLandlordEntity) {
            return UserSysRoleEnum.LANDLORD;
        } else if (userEntity instanceof UserTenantEntity) {
            return UserSysRoleEnum.TENANT;
        } else {
            throw new LoginException("不支持的操作");
        }
    }

    @Override
    public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {
        super.afterCompletion(request, response, exception);
        UserSysRoleEnum roleEnum = loginType.get();
        //默认租客,房东是无状态的请求,避免产生大量session所以要退出登录
        if (null != roleEnum && !UserSysRoleEnum.ADMIN.equals(roleEnum)) {
            ShiroUtils.logout();
        }
        loginType.remove();
    }
}
